package nachos.threads;

import nachos.machine.*;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Set;

/**
 * Uses the hardware timer to provide preemption, and to allow threads to sleep
 * until a certain time.
 */
public class Alarm {
    /**
     * Allocate a new Alarm. Set the machine's timer interrupt handler to this
     * alarm's callback.
     *
     * <p><b>Note</b>: Nachos will not function correctly with more than one
     * alarm.
     */
    public Alarm() {
    	Machine.timer().setInterruptHandler(new Runnable() {
		    public void run() { 
		      timerInterrupt();
  		  }
	    });
    }

    /**
     * The timer interrupt handler. This is called by the machine's timer
     * periodically (approximately every 500 clock ticks). Causes the current
     * thread to yield, forcing a context switch if there is another thread
     * that should be run.
     */
    public void timerInterrupt() {
    //original
//      Lib.debug(dbgAlarm,"In Interrupt Handler (time = "+Machine.timer().getTime()+")");
//  	  KThread.currentThread().yield();
    	
    	now = Machine.timer().getTime();
    	Lib.debug(dbgAlarm,"In Interrupt Handler (time = "+ now + ")");    	
    	
    	//Retrieve all threads with wake time up until now and add each to ready queue.
    	Set<Map.Entry<Long, KThread>> threadsToWake = sleepingThreads.headMap(Long.valueOf(now)).entrySet();
    	for (Map.Entry<Long, KThread> thread : threadsToWake) {
        assert(thread.getKey() <= now);
        Lib.debug(dbgAlarm,"Waking up " + thread.getValue() + " at " + Machine.timer().getTime());
        thread.getValue().ready();
      }
      //Trim the awaken threads from the list of sleeping threads.
      if (threadsToWake.size() > 0) {
        sleepingThreads = sleepingThreads.tailMap(Long.valueOf(now));
      }
      
    	Lib.debug(dbgAlarm,"Exiting Interrupt Handler (time = "+ Machine.timer().getTime() + ")");
      KThread.currentThread().yield();
    }

    /**
     * Put the current thread to sleep for at least <i>x</i> ticks,
     * waking it up in the timer interrupt handler. The thread must be
     * woken up (placed in the scheduler ready set) during the first timer
     * interrupt where
     *
     * <p><blockquote>
     * (current time) >= (WaitUntil called time)+(x)
     * </blockquote>
     *
     * @param	x	the minimum number of clock ticks to wait.
     *
     * @see	nachos.machine.Timer#getTime()
     */
    public void waitUntil(long x) {
    // This is a bad busy waiting solution 
        // long wakeTime = Machine.timer().getTime() + x;
        // while (wakeTime > Machine.timer().getTime())
        //    KThread.yield();
      
      long wakeTime = Machine.timer().getTime() + x;
	    Lib.debug(dbgAlarm, "waitUntil:: current time: " + Machine.timer().getTime());
	    Lib.debug(dbgAlarm, "waitUntil:: current thread: " + KThread.currentThread().getName());
	    Lib.debug(dbgAlarm, "waitUntil:: wake time: " + wakeTime);
	    
	    //Store wake up time and then sleep current thread.
      Machine.interrupt().disable();
      sleepingThreads.put((Long) wakeTime, KThread.currentThread());
      Lib.debug(dbgAlarm, sleepingThreads.toString());
      KThread.sleep();
    }

    /**
     * Tests whether this module is working.
     */
    public static void selfTest() {
    	AlarmTest.runTest();
    }

    private static final char dbgAlarm = 'a';
    
    private Long now;
    private SortedMap<Long, KThread> sleepingThreads = new TreeMap<Long, KThread>();
}
